#include "omelette.hpp"
#include "../search/astar.hpp"
#include <cstring>
#include <sstream>

using namespace omelette;

double omelette::BrownProb = 0;

double omelette::BadProb[2] = {
	0.5,	// White
	0.1,	// Brown eggs are local eggs, and local eggs are fresh.
};

bool State::operator==(const State &o) const {
	for (int i = 0; i < NBowls; i++) {
		if (bowls[i] != o.bowls[i])
			return false;
	}
	return hand == o.hand && rng == o.rng;
}

bool State::isgoal() const {
	return bowls[0].known && !bowls[0].bad && bowls[0].n == NEggs;
}

std::vector<Op> State::ops() const {
	std::vector<Op> os;

	if (hand != Empty) {
		for (int i = 0; i < NBowls; i++) {
			if (bowls[i].n + 1 <= NEggs)
				os.push_back(Op::breakegg(i));
		}
		return os;
	}

	os.push_back(Op::grab());

	for (int i = 0; i < NBowls; i++) {
		// The PDDL description allows cleaning and smelling
		//  an empty bowl.
		if (bowls[i].n <= 0)
			continue;

		os.push_back(Op::clean(i));
		os.push_back(Op::smell(i));

		for (int j = 0; j < NBowls; j++) {
			if (i != j && bowls[i].n + bowls[j].n <= NEggs)
				os.push_back(Op::pour(i, j));
		}
	}
	return os;	
}

State State::act(const Op &op) const {
	State res(*this);

	switch (op.kind) {
	case Op::Grab:
		assert (res.hand == Empty);
		assert (next == White || next == Brown);
		res.hand = res.rng.real() < BadProb[next] ? Bad : Good;
		res.handcolor = res.next;
		res.next = res.rng.real() < BrownProb ? Brown : White;
		break;

	case Op::BreakEgg: {
		assert (op.a < NBowls);
		assert (op.a >= 0);
		Bowl &b = res.bowls[op.a];
		assert (res.hand != Empty);
		assert (b.n + 1 <= NEggs);

		if (res.hand == Bad)
			b.bad = true;
		b.known = false;
		b.n++;
		if (res.handcolor == Brown)
			b.nbrown++;

		res.hand = Empty;
		break;
	}

	case Op::Pour: {
		assert (op.a < NBowls);
		assert (op.a >= 0);
		assert (op.b < NBowls);
		assert (op.b >= 0);
		assert (op.a != op.b);
		Bowl &src = res.bowls[op.a];
		Bowl &dst = res.bowls[op.b];
		assert (res.hand == Empty);
		assert (dst.n + src.n <= NEggs);

		dst.bad = dst.bad || src.bad;
		dst.known = dst.known && src.known;
		dst.n += src.n;
		dst.nbrown += src.nbrown;

		src.bad = false;
		src.known = true;
		src.n = 0;
		src.nbrown = 0;
		break;
	}

	case Op::Clean: {
		assert (op.a < NBowls);
		assert (op.a >= 0);
		Bowl &b = res.bowls[op.a];
		assert (hand == Empty);
		b.bad = false;
		b.known = true;
		b.n = 0;
		b.nbrown = 0;
		break;
	}

	case Op::Smell: {
		assert (op.a < NBowls);
		assert (op.a >= 0);
		Bowl &b = res.bowls[op.a];
		assert (hand == Empty);
		b.known = true;
		break;
	}

	default:
		fatal("Unknown operator kind: %d", op.kind);
	}
	return res;
}

State State::future() const {
	State f(*this);
	f.rng = Rand(randgen.bits());
	if (f.hand != Empty)
		f.hand = f.rng.real() < BadProb[f.handcolor] ? Bad : Good;

	for (int i = 0; i < NBowls; i++) {
		Bowl &b = f.bowls[i];
		if (b.known)
			continue;
		for (int j = 0; !b.bad && j < b.n; j++) {
			if (j < b.nbrown)
				b.bad = f.rng.real() < BadProb[Brown];
			else
				b.bad = f.rng.real() < BadProb[White];
		}
	}

	return f;
}

unsigned long State::hash() const {
	unsigned long h = 0;
	for (int i = 0; i < NBowls; i++) {
		h = (h << 1) | bowls[i].known;
		h = (h << 1) | bowls[i].bad;
		h = (h << 1) | bowls[i].n;
		h = (h << 1) | bowls[i].nbrown;
	}
	h = (h << 1) | hand;
	h = (h << 1) | next;
	h = (h << 1) | rng.v;
	h = (h << 1) | rng.theseed;
	return h;
}

std::string State::str() const {
	std::stringstream s;

	s << "state: hash=" << hash() << ", v=" << rng.v << ", seed=" << rng.theseed << std::endl;

	const char *hc = "white";
	if (handcolor == Brown);
		hc = "brown";

	switch (hand) {
	case Empty:
		s << "\thand: empty" << std::endl;
		break;
	case Bad:
		s << "\thand: bad " << hc << " egg"<< std::endl;
		break;
	case Good:
		s << "\thand: good " << hc << " egg" << std::endl;
		break;
	}

	switch (next) {
	case White:
		s << "\tnext: white" << std::endl;
		break;
	case Brown:
		s << "\tnext: brown" << std::endl;
		break;
	}

	for (int i = 0; i < NBowls; i++) {
		s << "\tbowls[" << i << "]: ";
		s << "n=" << bowls[i].n << " (" << bowls[i].nbrown << ") ";
		s << "bad=" << bowls[i].bad << ", ";
		s << "known=" << bowls[i].known << std::endl;
	}

	return s.str();
}

std::string Op::str() const {
	std::stringstream s;

	switch (kind) {
	case None:
		s << "None";
		break;

	case Grab:
		s << "Grab";
		break;

	case BreakEgg:
		s << "Break egg into " << a;
		break;

	case Pour:
		s << "Pour " << a << " into " << b;
		break;

	case Clean:
		s << "Clean " << a;
		break;

	case Smell:
		s << "Smell " << a;
		break;

	default:
		s << "Unknown kind=" << kind << ", a=" << a << ", b=" << b;
		break;
	}

	return s.str();
}

const Op omelette::Op::Nop(None, 0, 0);

class OmeletSearch {
public:
	typedef int Cost;
	typedef omelette::Op Oper;
	static const omelette::Op Nop;
	typedef omelette::State State;
	typedef State PackedState;

	OmeletSearch(const State &i) : init(i) {
	}

	unsigned long hash(const PackedState &s) const {
		return s.hash();
	}

	State initialstate() const {
		return init;
	}

	Cost h(const State&) const {
		return 0;
	}

	bool isgoal(const State &s) const {
		return s.isgoal();
	}

	struct Operators {
		std::vector<Oper> ops;

		Operators(const OmeletSearch&, const State &s) : ops(s.ops()) {
		}

		unsigned int size() const {
			return ops.size();
		}

		Oper operator[](unsigned int i) const {
			return ops.at(i);
		}
	};

	struct Edge {
		Cost cost;
		Oper revop;
		State state;

		Edge(const OmeletSearch&, const State &s, Oper op) :
			cost(1), revop(Nop), state(s.act(op)) {
		}
	};

	void pack(PackedState &dst, State &src) const {
		dst = src;
	}

	State &unpack(State&, PackedState &pkd) const {
		return pkd;
	}

	void dumpstate(FILE *out, const State &s) const {
		fatal("Unimplemented");
	}

private:
	State init;
};

const OmeletSearch::Oper OmeletSearch::Nop = omelette::Op::Nop;

std::vector<Op> omelette::solve(const State &init) {
	Astar<OmeletSearch> astar(0, NULL);
	static const char *l[] = { "-mem", "3G" };
	astar.lim = Limit(2, l);

	OmeletSearch o(init);
	State s = o.initialstate();
	astar.search(o, s);
	return astar.res.ops;
}
